open Actor;
open Type;
exception FoundPosition of point;
type cell = [= `Block | `Occupation of list actor];
type move_result = [=`Ok | `Blocked  | `Occupied of list actor];
class map width height =
  object(self)
    value brd : array (array cell) = Array.create_matrix width height (`Occupation []);
    method size = (width,height);
    method brd = brd;
    method set_cell (x,y) cell = brd.(x).(y) := cell;
    method cell (x,y) =  brd.(x).(y);
    method free_point =
      get_free 0 where
        rec get_free i =
          let pos = ((Random.int (width-2))+1,(Random.int (height-2))+1) in
          match self#cell pos with
          [ `Block when i = 100 -> failwith "Not found free place in map"
          | `Block -> get_free (i+1)
          | _ -> pos
          ];

    method search_path start (xx,yy) =
      let estimate_remaining_cost (x,y) =
        let dx = xx - x in
        let dy = yy - y in
        (abs dx) + (abs dy)
      in
      let generate_successor_states (x,y) =
        ExtLib.List.filter_map begin fun (dx,dy) ->
          let p = (x+dx,y+dy) in
          try
            match self#cell p with
            [ `Occupation [] -> 
              Some 
              {
                Astar.next_state = p;
                cost = 1
              }
            | `Block -> None
            | _ ->
              Some 
              {
                Astar.next_state = p;
                cost = 10
              }
            ]
          with [ Invalid_argument "index out of bounds" -> None]
        end [(-1,-1);(0,-1);(1,-1);(-1, 0);(1, 0);(-1, 1);(0, 1);(1, 1)]
      in
      Astar.a_star start generate_successor_states estimate_remaining_cost;

    initializer
    (
      for x=0 to width-1 do 
        (
          self#set_cell (x,0) `Block; 
          self#set_cell (x,height-1) `Block;
          if x = width /2 then () else self#set_cell (x,height/2) `Block
        ) 
      done;
(*       self#set_cell (28,28) `Block; *)
      for y=0 to height-1 do (self#set_cell (0,y) `Block; self#set_cell (width-1,y) `Block) done;
    );

    method m_print =
      for j=0 to height-1 do  
      (
        for i=0 to width-1 do 
          Printf.printf "%s"
          (
            match brd.(i).(j) with
            [ `Block -> "X"
            | `Occupation [] -> " "
            | `Occupation lst when MList.for_all (fun a -> a#state = `Die) lst -> "D"
            | `Occupation lst when MList.for_all (fun a -> a#state = `Stun or a#state = `Die) lst -> "S"
            | `Occupation [a] when a#state = `Run -> "R"
            | `Occupation [a] when a#state = `Walk -> "W"
            | `Occupation lst -> 
              match MList.find_all (fun a -> a#state = `Run) lst with
              [ [] ->
                match MList.find_all (fun a -> a#state = `Walk) lst with
                [ [] -> assert False
                | _ -> "W"
                ]
              | _ ->
                match MList.find_all (fun a -> a#state = `Walk) lst with
                [ [] -> "R"
                | _ -> assert False
                ]
              ]
            ]
          )
        done;
        Printf.printf "\n"
      )
      done;

  method position a =
    try
    (
      for j=0 to height-1 do  
        for i=0 to width-1 do 
            match brd.(i).(j) with
            [ `Occupation lst when List.mem a lst -> raise (FoundPosition (i,j))
            | _ -> ()
            ]
        done;
      done;
      failwith (Printf.sprintf "actor %s not found" a#name);
    )
    with [FoundPosition pos -> pos];

  method next_step_opt (a:actor) =
    match a#path with
    [ [] -> None
    | [pos::tl] -> Some (pos,tl)
    ];

  method can_next_step (a:actor) =
    match self#next_step_opt a with
    [ None -> False
    | Some (p,_) ->
        match self#cell p with
        [ `Occupation [] -> True
        | `Occupation lst -> MList.for_all (fun a -> a#state = `Stun) lst
        | _ -> assert False
        ]
    ];

  method real_move a next_pos : move_result =
    let curr_pos = self#position a in
    match (self#cell curr_pos, self#cell next_pos) with
    [(`Occupation curr_lst,`Occupation next_lst) ->
      match MList.partition (fun a -> a#state = `Stun or a#state = `Die) next_lst with
      [ (stuns,[]) ->
        (
          self#set_cell curr_pos (`Occupation (MList.remove curr_lst a));
          self#set_cell next_pos (`Occupation [a::next_lst]);
          List.iter (fun a -> if a#state = `Stun then a#dec_stamina 1 else ()) stuns;
          `Ok
        )
      | (_,lst) -> `Occupied lst
      ]
    | (_,`Block) -> `Blocked
    | _ -> assert False
    ];

end;
(*}}}*)